CloudFront+S3 OACの設定をCloudFormationでやってみた
静的コンテンツを配信するためにCloudFrontとS3を利用した際、OACを利用したのでブログに残しておきます。
OACとは
CloudFrontのオリジンにS3を指定する際、S3へのアクセスをCloudFrontからだけに制限するための設定です。
S3へのアクセス制限はOAIという機能でも可能ですがOACでは以下のものに対応しています。
Amazon S3 オリジンへのアクセスの制限
AWS KMS による Amazon S3 サーバー側の暗号化 (SSE-KMS)
Amazon S3 に対する動的なリクエスト (POST、PUT など)
詳しくは以下のブログとドキュメントをご確認ください。
作成したCloudFormationテンプレート
以下のテンプレートをデプロイするとCloudFrontとS3が作成されます。
今回はS3とCloudFrontでテンプレートを分割しました。
デプロイをする際は順番があるのですぐに試したい場合はデプロイに飛んでください。
S3のテンプレート
S3バケットはKMS暗号化を使用して作成しました。
AWS::KMS::Key
AWS::S3::Bucket
AWSTemplateFormatVersion: "2010-09-09" Description: S3&KMS Stack Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# BucketName: Type: String IAMUserARN: Type: String Resources: # ------------------------------------------------------------# # KMS # ------------------------------------------------------------# KMS: Type: AWS::KMS::Key Properties: Description: OAC Test Enabled: true KeyPolicy: Version: '2012-10-17' Statement: - Sid: Enable IAM User Permissions Effect: Allow Principal: AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: 'kms:*' Resource: '*' - Sid: 'Allow access for Key Administrators' Effect: Allow Principal: AWS: !Ref IAMUserARN Action: - 'kms:Create*' - 'kms:Describe*' - 'kms:Enable*' - 'kms:List*' - 'kms:Put*' - 'kms:Update*' - 'kms:Revoke*' - 'kms:Disable*' - 'kms:Get*' - 'kms:Delete*' - 'kms:TagResource' - 'kms:UntagResource' - 'kms:ScheduleKeyDeletion' - 'kms:CancelKeyDeletion' - 'kms:CreateGrant' - 'kms:ListGrants' - 'kms:RevokeGrant' Resource: '*' - Sid: 'Allow use of the key' Effect: Allow Principal: AWS: !Ref IAMUserARN Action: - 'kms:Encrypt' - 'kms:Decrypt' - 'kms:ReEncrypt*' - 'kms:GenerateDataKey*' - 'kms:DescribeKey' Resource: '*' - Sid: 'Allow use of the key' Effect: Allow Principal: Service: - cloudfront.amazonaws.com Action: - 'kms:Decrypt' - 'kms:Encrypt' - 'kms:GenerateDataKey*' Resource: '*' Condition: StringEquals: aws:SourceArn: - !Join - '' - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/' - !ImportValue CloudFrontID PendingWindowInDays: 7 # ------------------------------------------------------------# # S3 # ------------------------------------------------------------# S3: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - BucketKeyEnabled: true ServerSideEncryptionByDefault: KMSMasterKeyID: !Ref KMS SSEAlgorithm: aws:kms BucketName: !Ref BucketName PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True S3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref S3 PolicyDocument: Version: "2008-10-17" Statement: - Sid: "AllowCloudFrontServicePrincipal" Effect: "Allow" Principal: Service: - "cloudfront.amazonaws.com" Action: - "s3:GetObject" Resource: - !Sub ${S3.Arn}/* Condition: StringEquals: AWS:SourceArn: - !Join - '' - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/' - !ImportValue CloudFrontID Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# S3: Value: !GetAtt S3.DomainName Export: Name: S3
CloudFrontのテンプレート
CloudFrontでS3をオリジンにする際は「CustomOriginConfig」(静的Webサイトホスティングを設定している場合) もしくは「S3OriginConfig」の設定が必要です。
今回は静的Webサイトホスティングではないので「S3OriginConfig」を設定しています。
また「S3OriginConfig」の中にある「OriginAccessIdentity」は空の要素を指定してオリジン アクセスIDを利用しない状態を作っています。
OAIは使用しませんが、記載しないとエラーになります。
AWS::CloudFront::Distribution
AWS::CloudFront::OriginAccessControl
AWSTemplateFormatVersion: "2010-09-09" Description: CloudFront Stack Resources: # ------------------------------------------------------------# # CloudFront # ------------------------------------------------------------# OAC: Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Description: Access Control Name: OAC OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4 CloudFront: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: AllowedMethods: - GET - HEAD CachedMethods: - GET - HEAD CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 TargetOriginId: S3 ViewerProtocolPolicy: allow-all Enabled: true Origins: - DomainName: !ImportValue S3 Id: S3 OriginAccessControlId: !GetAtt OAC.Id S3OriginConfig: OriginAccessIdentity: '' PriceClass: PriceClass_200 Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# CloudFrontID: Value: !Ref CloudFront Export: Name: CloudFrontID
デプロイ
デプロイはAWS CLIを使用して行いました。
まずはS3をデプロイします。
S3を最初にデプロイする際は以下のようにKMSキーポリシーのCloudFrontに関係する部分とS3バケットポリシーをコメントへ変更しておきます。
理由としてはCloudFrontのARNを指定しているためコメントにしていない場合、作成時にエラーが発生するためです。
AWSTemplateFormatVersion: "2010-09-09" Description: S3&KMS Stack Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# BucketName: Type: String IAMUserARN: Type: String Resources: # ------------------------------------------------------------# # KMS # ------------------------------------------------------------# KMS: Type: AWS::KMS::Key Properties: Description: OAC Test Enabled: true KeyPolicy: Version: '2012-10-17' Statement: - Sid: Enable IAM User Permissions Effect: Allow Principal: AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: 'kms:*' Resource: '*' - Sid: 'Allow access for Key Administrators' Effect: Allow Principal: AWS: !Ref IAMUserARN Action: - 'kms:Create*' - 'kms:Describe*' - 'kms:Enable*' - 'kms:List*' - 'kms:Put*' - 'kms:Update*' - 'kms:Revoke*' - 'kms:Disable*' - 'kms:Get*' - 'kms:Delete*' - 'kms:TagResource' - 'kms:UntagResource' - 'kms:ScheduleKeyDeletion' - 'kms:CancelKeyDeletion' - 'kms:CreateGrant' - 'kms:ListGrants' - 'kms:RevokeGrant' Resource: '*' - Sid: 'Allow use of the key' Effect: Allow Principal: AWS: !Ref IAMUserARN Action: - 'kms:Encrypt' - 'kms:Decrypt' - 'kms:ReEncrypt*' - 'kms:GenerateDataKey*' - 'kms:DescribeKey' Resource: '*' # - Sid: 'Allow use of the key' # Effect: Allow # Principal: # Service: # - cloudfront.amazonaws.com # Action: # - 'kms:Decrypt' # - 'kms:Encrypt' # - 'kms:GenerateDataKey*' # Resource: '*' # Condition: # StringEquals: # aws:SourceArn: # - !Join # - '' # - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/' # - !ImportValue CloudFrontID PendingWindowInDays: 7 # ------------------------------------------------------------# # S3 # ------------------------------------------------------------# S3: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - BucketKeyEnabled: true ServerSideEncryptionByDefault: KMSMasterKeyID: !Ref KMS SSEAlgorithm: aws:kms BucketName: !Ref BucketName PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True # S3BucketPolicy: # Type: AWS::S3::BucketPolicy # Properties: # Bucket: !Ref S3 # PolicyDocument: # Version: "2008-10-17" # Statement: # - Sid: "AllowCloudFrontServicePrincipal" # Effect: "Allow" # Principal: # Service: # - "cloudfront.amazonaws.com" # Action: # - "s3:GetObject" # Resource: # - !Sub ${S3.Arn}/* # Condition: # StringEquals: # AWS:SourceArn: # - !Join # - '' # - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/' # - !ImportValue CloudFrontID Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# S3: Value: !GetAtt S3.DomainName Export: Name: S3
1. S3作成
以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=BucketName,ParameterValue=S3バケット名 ParameterKey=IAMUserARN,ParameterValue=IAMユーザのARN
2. CloudFront作成
S3のスタックが完成したらCloudFrontを作成していきます。
CloudFrontのテンプレートは編集せずに以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名
3. S3バケットポリシーとKMSポリシー追加
CloudFrontの作成が完了したらコメントに変更していたS3バケットポリシーとKMSポリシーの部分を戻して以下のコマンドを実行します。
aws cloudformation create-change-set --change-set-name 変更セット名 --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=BucketName,ParameterValue=S3バケット名 ParameterKey=IAMUserARN,ParameterValue=IAMユーザのARN aws cloudformation execute-change-set --change-set-name 変更セットのARN
4. 動作確認
以下のHTMLファイルをS3にアップロードします。
<html> <head> <meta charset="UTF-8"> <title>テストページ</title> </head> <body bgcolor="#10100E" text="#cccccc"> 小林 陸<br/> </body> </html>
S3へのアップロードは以下のコマンドを実行します。
test.htmlはアップロードするHTMLファイル名になります。
aws s3 cp ./test.html s3://バケット名
ファイルアップロード後CloudFrontのドメイン名でブラウザからアクセスすると以下のようにページが表示されます。
オブジェクトURLに直接アクセスしても表示されないことも確認できます。
テンプレート説明
今回作成したCloudFormationテンプレートで重要な部分を説明します。
CloudFrontのテンプレートでは9行目から記載しているOACを作成している部分と36行目のOACを指定している部分が今回メインになります。
OACの作成は「AWS::CloudFront::OriginAccessControl」というものを使用して作成します。
各パラメータでOACの名前、オリジンのタイプ、オリジンへのリクエストに署名をする設定、署名方法を設定しています。
少し内容がずれますが、普段AWSのAPIを利用するときは裏側でSigV4というもので署名を行いAPIリクエスト実行者の認証、承認を行っています。
この機能を使ってCloudFrontからS3オリジンへのアクセスに認証、承認、拒否を行いアクセスを制御しています。
署名についての詳細は以下のドキュメントをご確認ください。
署名バージョン 4 の署名プロセス
Authenticating Requests (AWS Signature Version 4)
# OAC作成部分 OAC: Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Description: Access Control Name: OAC OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4 # OAC指定部分 OriginAccessControlId: !GetAtt OAC.Id
S3のテンプレートではKMSキーポリシーとS3バケットポリシーが重要な部分になっています。
キーポリシーではCloudFrontのプリンシパルを指定してKMSにアクセスできるようにしています。
S3バケットポリシーも同様にCloudFrontからアクセスできるようにプリンシパルで指定しています。
# キーポリシー抜粋 - Sid: 'Allow use of the key' Effect: Allow Principal: Service: - cloudfront.amazonaws.com Action: - 'kms:Decrypt' - 'kms:Encrypt' - 'kms:GenerateDataKey*' Resource: '*' Condition: StringEquals: aws:SourceArn: - !Join - '' - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/' - !ImportValue CloudFrontID # S3バケットポリシー - Sid: "AllowCloudFrontServicePrincipal" Effect: "Allow" Principal: Service: - "cloudfront.amazonaws.com" Action: - "s3:GetObject" Resource: - !Sub ${S3.Arn}/* Condition: StringEquals: AWS:SourceArn: - !Join - '' - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/' - !ImportValue CloudFrontID
さいごに
今回はCloudFrontのOACをCloudFormationで作成してみました。
CloudFormationではなくマネジメントコンソールからの作成の場合、バケットポリシーをコピーすることが可能なので簡単に設定することができます。
以下のドキュメントにOAIからOACへ移行する際のサンプルバケットポリシーが記載されていますので移行を検討の際はご確認ください。